﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Hosting;
using System.Web.WebPages;
using Eksponent.RazorRocks.Helpers;

namespace Eksponent.RazorRocks
{
    public partial class TemplateMatcher<TItem> where TItem : class
    {
        public ConfigSetter Configure()
        {
            return new ConfigSetter(this);
        }
        
       
        #region Nested type: ConfigSetter

        public class ConfigSetter
        {
            private readonly TemplateMatcher<TItem> _matcher;

            private string[] _prevSubfolders = null;
            private string[] _subfolders = null;
            private string[] _typeNames = null;
            private string[] _prevTypeNames = null;
            private bool _addMatches = false;
            

            internal ConfigSetter(TemplateMatcher<TItem> matcher)
            {
                _matcher = matcher;
            }

            /// <summary>
            /// Includes the configuration settings in the specified file.
            /// The output is discareded, so it is only supposed to contain configuration code
            /// </summary>
            /// <param name="path">The path relative to the TemplateMatcher's BasePath.</param>
            public ConfigSetter Include(string path)
            {
                var page = (WebPageBase)WebPageContext.Current.Page;
                path = _matcher._basePath + path;

                using (var dummy = new StreamWriter(Stream.Null))
                {
                    page.RenderPage(path).WriteTo(dummy);
                }

                return this;
            }


            public ConfigSetter ForSubfolders(params string[] subfolders)
            {
                if (_subfolders != null)
                {
                    throw new InvalidOperationException("Subfolders are already defined for the next call's context");
                }
                _subfolders = subfolders;

                return this;
            }
            

            /// <summary>
            /// Use this to add more type definitions to the subfolders given by the last call to ForSubfolders
            /// </summary>
            /// <returns></returns>
            public ConfigSetter And
            {
                get
                {
                    _subfolders = _prevSubfolders;

                    return this;
                }
            }

            /// <summary>
            /// Use this to add more match specifications to the types given by the last call to Types
            /// </summary>
            public MatchSetter Else
            {
                get
                {
                    _typeNames = _prevTypeNames;
                    _addMatches = true;
                    return new MatchSetter(this);
                }
            }

            /// <summary>
            /// Define rules for all types before their own rules are applied
            /// </summary>
            public MatchSetter AllTypes
            {
                get
                {
                    _typeNames = new[] { _matcher._defaultApriori.Name };
                    return new MatchSetter(this);
                }
            }

            /// <summary>
            /// Define rules to apply for types if none of their own rules applied
            /// </summary>
            public MatchSetter Default
            {
                get
                {
                    _typeNames = new[] {_matcher._defaultApostiori.Name };
                    return new MatchSetter(this);
                }
            }

            public MatchSetter Nulls
            {
                get
                {
                    _typeNames = new[] { _matcher._nullKey.Name };
                    return new MatchSetter(this);
                }
            }


            /// <summary>
            /// Define rules for the types specified
            /// </summary>
            /// <param name="typeNames">The type names.</param>
            /// <returns></returns>
            public MatchSetter Types(params string[] typeNames)
            {
                if (_typeNames != null)
                {
                    throw new InvalidOperationException("Types are already defined for the next call's context");
                }
                _typeNames = typeNames;

                return new MatchSetter(this);
            }

            /// <summary>
            /// Sets the base path for templates.
            /// </summary>
            /// <param name="basePath">The base path.</param>
            /// <returns></returns>
            public ConfigSetter SetBasePath(string basePath)
            {
                _matcher._basePath = basePath;
                return this;
            }

            /// <summary>
            /// Sets the max nesting depth in recursions. Default is 30.
            /// </summary>
            /// <param name="maxNestingDepth">The max nesting depth.</param>
            /// <returns></returns>
            public ConfigSetter SetMaxNestingDepth(int maxNestingDepth)
            {
                _matcher._maxNestingDepth = maxNestingDepth;

                return this;
            }
            

            public ConfigSetter MonitorTemplateChanges()
            {
                //TODO: Implement a timer that does the same to support Medium Trust

                var templateWatcher = new FileSystemWatcher(HostingEnvironment.MapPath(_matcher._basePath));

                templateWatcher.Deleted += (o, e) => ClearTemplateCache();
                templateWatcher.Renamed += (o, e) => ClearTemplateCache();
                templateWatcher.Created += (o, e) => ClearTemplateCache();
                templateWatcher.EnableRaisingEvents = true;
                
                _matcher._templateWatcher = templateWatcher;
                
                return this;
            }                       
            
            private ConfigSetter Matches(params TemplateController<TItem>[] templateResolvers)
            {
                foreach (var typeName in _typeNames)
                {
                    var name = typeName;                    
                    
                    var keys = _subfolders.IsNullOrEmpty()
                                    ? new[] { new MultiKey(name) }
                                    : _subfolders.Select(x => new MultiKey(name, x));

                    foreach (var key in keys)
                    {                        
                        if (_addMatches)
                        {
                            if (templateResolvers != null)
                            {
                                _matcher._templates.GetOrDefault(key).AddRange(templateResolvers);
                            }
                        }
                        else
                        {
                            if (templateResolvers == null)
                            {
                                _matcher._templates.Remove(key);
                            }
                            else
                            {
                                //NOTE: Bad idea. A call to Match without Else will simple override whatever already defined
                                //if (_matcher._templates.ContainsKey(key))
                                //{
                                //    throw new InvalidOperationException(
                                //        "Rules are already defined for the subfolder \"" + " and the type \"" + name +
                                //        "\". Use the Else method to combine different rules or Reset to clear");
                                //}
                                //_matcher._templates.Add(key, templateControllers.ToList());
                                _matcher._templates[key] = templateResolvers.ToList();
                            }
                        }
                    }
                }

                _prevSubfolders = _subfolders;
                _prevTypeNames = _typeNames;
                _subfolders = null;
                _typeNames = null;
                _addMatches = false;

                return this;
            }


            public class MatchSetter
            {
                private readonly ConfigSetter _owner;

                internal MatchSetter(ConfigSetter owner)
                {
                    _owner = owner;
                }

                /// <summary>
                /// Resets the rules for the types to the default
                /// </summary>
                /// <returns></returns>
                public ConfigSetter Reset()
                {
                    return _owner.Matches(null);
                }

                public ConfigSetter HaveNoMatch()
                {
                    return _owner.Matches(new TemplateController<TItem>[0]);
                }

                /// <summary>
                /// Matches the types to the specified template names.
                /// </summary>
                /// <param name="templateNames">The template names.</param>
                /// <returns></returns>
                public ConfigSetter Match(params string[] templateNames)
                {
                    return MatchIf(null, templateNames);
                }

                /// <summary>
                /// Matches the types to the specified tempaltes if the guard function returns true.
                /// </summary>
                /// <param name="guard">The guard.</param>
                /// <param name="templateNames">The template names.</param>
                /// <returns></returns>
                public ConfigSetter MatchIf(Func<TItem, bool> guard, params string[] templateNames)
                {
                    return MatchInternal(guard, templateNames.Select(x => (TemplateAction<TItem>) x).ToArray());                    
                }

                /// <summary>
                /// Matches the types to the template name returned by the specified function
                /// </summary>
                /// <param name="templateSwitch">The switch delegate.</param>
                /// <returns></returns>
                public ConfigSetter Match(Func<TItem, string> templateSwitch)
                {
                    return _owner.Matches(new TemplateController<TItem>((item, ps)=> templateSwitch(item)));
                }

                /// <summary>
                /// Matches the types to the specified razor delegate.
                /// </summary>
                /// <param name="razorDelegate">The razor delegate.</param>
                /// <returns></returns>
                public ConfigSetter Match(Func<TItem, HelperResult> razorDelegate)
                {
                    return MatchIf(null, razorDelegate);
                }

                /// <summary>
                /// Matches the types to the specified razor delegate if the guard function returns true.
                /// </summary>
                /// <param name="guard">The guard.</param>
                /// <param name="razorDelegate">The razor delegate.</param>
                /// <returns></returns>
                public ConfigSetter MatchIf(Func<TItem, bool> guard, Func<TItem, HelperResult> razorDelegate)
                {
                    return MatchInternal(guard, new DelegateTemplateAction<TItem>(
                                                    (item, ps) => razorDelegate(item)));
                }


                /// <summary>
                /// Matches the types to the specified razor delegate.
                /// Here the razor delegate is wrapped in a lambda expressions that allows to parameters to be accessed
                /// </summary>
                /// <param name="razorDelegate">The razor delegate.</param>
                /// <returns></returns>
                public ConfigSetter Match(Func<dynamic, Func<TItem, HelperResult>> razorDelegate)
                {
                    return MatchIf(null, razorDelegate);
                }

                /// <summary>
                /// Matches the types to the specified razor delegate if the guard function returns true.
                /// Here the razor delegate is wrapped in a lambda expressions that allows to parameters to be accessed
                /// </summary>
                /// <param name="guard">The guard.</param>
                /// <param name="razorDelegate">The razor delegate.</param>
                /// <returns></returns>
                public ConfigSetter MatchIf(Func<TItem, bool> guard, Func<dynamic, Func<TItem, HelperResult>> razorDelegate)
                {                    
                    return MatchInternal(guard, new DelegateTemplateAction<TItem>(
                                                    (item, ps) => razorDelegate(ps)(item)));
                }


                /// <summary>
                /// Matches the types to the controller delegates specified
                /// </summary>
                /// <param name="actions">The controller delegates</param>
                /// <returns></returns>
                public ConfigSetter Match(params Func<TItem, IDictionary<string, object>, TemplateAction<TItem>>[] actions)
                {                    
                    return _owner.Matches(actions.Select(x=>new TemplateController<TItem>(x)).ToArray());
                }

                /// <summary>
                /// Matches the types to the controllers specified
                /// </summary>
                /// <param name="controllers">The controllers</param>
                /// <returns></returns>
                public ConfigSetter Match(params TemplateController<TItem>[] controllers)
                {
                    if( controllers.IsNullOrEmpty() )
                    {
                        throw new ArgumentException("At least one function must be specified");
                    }

                    return _owner.Matches(controllers);
                }


                /// <summary>
                /// Generic template method for the public Match/MatchIf methods
                /// </summary>
                /// <param name="guard"></param>
                /// <param name="actions"></param>
                /// <returns></returns>
                private ConfigSetter MatchInternal(Func<TItem, bool> guard, params TemplateAction<TItem>[] actions)
                {
                    if (guard != null)
                    {
                        return _owner.Matches(actions.Select(x =>
                                                             new TemplateController<TItem>((item, ps) =>
                                                                                           guard(item) ? x : null)).
                                                  ToArray());
                    }
                    else
                    {
                        return _owner.Matches(actions.Select(x => (TemplateController<TItem>)x).ToArray());
                    }
                }
            }
        }
        

        #endregion
    }
}
